package com.zhentao.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.StringUtils;
import com.zhentao.mapper.TaskInfoLogsMapper;
import com.zhentao.mapper.TaskInfoMapper;
import com.zhentao.model.common.dtos.ResponseResult;
import com.zhentao.model.schedule.dto.TaskDto;
import com.zhentao.model.schedule.pojo.TaskInfo;
import com.zhentao.model.schedule.pojo.TaskInfoLogs;
import com.zhentao.service.TaskInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Date 2024/5/25 9:39
 * @Author xdm
 **/
@Service
@Slf4j
public class TaskInfoServiceImpl implements TaskInfoService {

    @Autowired
    private TaskInfoMapper taskInfoMapper;

    @Autowired
    private TaskInfoLogsMapper taskInfoLogsMapper;
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public ResponseResult addTask(TaskDto taskDto) {
        //添加数据库： 任务表+任务日志表   任务1-任务表   添加日志表【1 什么时候添加的】
        TaskInfo taskInfo = addTaskToDB(taskDto);
        //把任务添加到redis里面  如果小于=当前时候，立即执行-》list  如果是未来要执行的--》zset里面

        taskDto.setTaskId(taskInfo.getTaskId());
        addTaskToRedis(taskDto);
        return ResponseResult.okResult(taskInfo);
    }


    public void addTaskToRedis(TaskDto taskDto) {
     /*   把任务添加到redis里面
          如果任务的执行时间小于=当前时候，立即执行-》list 消息队列里面 --》然后再去消费 写一个定时任务 1s一次，<=当前时间的，立马执行
        如果是未来要执行的--》zset里面   ？ 是所有的数据都放到zset里面吗 ？
                未来5分钟的数据放到zSet里面，也写一个定时任务 1分钟扫描一次，如果zset里面得任务在1分钟之内，放到list里面

        大于5分钟数据怎么办，在数据库里面； 然后写一个定时任务去扫描数据库，5分钟扫描一次，如果数据库里面得任务在5分钟之内，放到zSet里面
    */
        //判断时间是在在5分钟之内，获取到5分钟之后的毫秒值
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        Long nextTime = calendar.getTimeInMillis();


        //定义key  1.发布文章  "submit:current:1     2.审核  submit:current:2
        String listKey="submit:current:"+taskDto.getTaskType();
        String zsetKey="submit:future:"+taskDto.getTaskType();

        if(taskDto.getExecuteTime()<=System.currentTimeMillis()){
            //如果小于=当前时间  -->list里面

            log.info("放到list{},{}",taskDto,new Date().toLocaleString());
            redisTemplate.opsForList().leftPush(listKey, JSON.toJSONString(taskDto));
        }else if(taskDto.getExecuteTime()<=nextTime){
            //5分钟之内的数据  -->zset里面

            log.info("放到zSet{},{}",taskDto,new Date().toLocaleString());
            redisTemplate.opsForZSet().add(zsetKey,JSON.toJSONString(taskDto),taskDto.getExecuteTime());
        }





    }


    public TaskInfo addTaskToDB(TaskDto dto) {
        //任务表
        TaskInfo taskInfo = new TaskInfo();
        BeanUtils.copyProperties(dto, taskInfo);
        taskInfo.setExecuteTime(new Date(dto.getExecuteTime()));
        taskInfoMapper.insert(taskInfo);

        //任务日志表
        TaskInfoLogs taskInfoLogs = new TaskInfoLogs();
        BeanUtils.copyProperties(taskInfo, taskInfoLogs);
        taskInfoLogs.setVersion(1);
        taskInfoLogs.setStatus(0);
        taskInfoLogsMapper.insert(taskInfoLogs);

        return taskInfo;

    }




    //zet--- list里面
    @Scheduled(cron = "0 */1 * * * ?")
    public void zSetToList() {
        if (zSetlock()) {


            log.info("把zset里面得数据刷新到list里面，{}", new Date().toLocaleString());

            //1.获取到当前要消费的数据
            String zsetKey = "submit:future:" + 1;
            Set<Object> tasks = redisTemplate.opsForZSet().rangeByScore(zsetKey, 0, System.currentTimeMillis());

           /* //2.把数据放到list里面
            for (Object task : tasks) {
                String listKey = "submit:current:" + 1;
                //1.放到list里面
                redisTemplate.opsForList().leftPush(listKey, task);

                //2.把放入list里面得数据从zSet里面删除掉
                redisTemplate.opsForZSet().remove(zsetKey, task);
            }*/

            //使用pileLine的形式处理

            redisTemplate.executePipelined(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    for (Object task : tasks) {
                        String listKey = "submit:current:" + 1;
//                       1 放到list里面
                        redisTemplate.opsForList().leftPush(listKey,task);

//                        2 把放入list里面的数据从zSet里面删除掉
                        redisTemplate.opsForZSet().remove(zsetKey,task);
                    }
                    return null;
                }
            });
        }

    }

    //加锁
    public Boolean zSetlock(){
//        setNx key
        String key = "Lock:zSetName";
        //值
        String value = UUID.randomUUID().toString();

//        使用setNX方法，如果key不存在返回true，如果key存在返回false
        //为了防止永远进不来，给他设置一个过期时间
        try {
            Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, 5000, TimeUnit.MILLISECONDS);
            if (b) { //加锁成功
                return true;
            }else {
                return false;
            }
        } finally {
            //什么时候释放锁  不管出不出异常都要释放锁
            //怎么释放锁 确保谁加的锁谁去释放锁
            if (value.equals(redisTemplate.opsForValue().get(key))) {
                redisTemplate.delete(key);
            }
        }
    }




    /**
     * 取数据
     *
     * @param type
     * @return
     */
    @Override
    public ResponseResult submitCurrentPoll(int type) {
        String listKey = "submit:current:" + type;   //固定就是1
        String task = (String) redisTemplate.opsForList().rightPop(listKey);
        if(StringUtils.isNotBlank(task)){

            TaskDto taskDto= JSON.parseObject(task,TaskDto.class);

            return ResponseResult.okResult(taskDto);
        }
        return null;
    }

}